"use strict";

const log = require("@test-cli/log");
const Command = require("@test-cli/command");
const fs = require("fs");
const fse = require("fs-extra");
const inquirer = require("inquirer");
const semver = require("semver");
const Package = require("@test-cli/package");
const userHome = require("user-home");
const path = require("path");
const { spinnerStart, sleep, execAsync } = require("@test-cli/utils");

const getTemplate = require("./getTemplate");

const TYPE_PROJECT = "project";
const TYPE_COMPONENT = "component";
const TEMPLATE_TYPE_NORMAL = "normal";
const TEMPLATE_TYPE_CUSTOM = "custom";
const WHITE_COMMAND = ["npm", "cnpm"];

class InitCommand extends Command {
  init() {
    this.configName = this._argv[0] || "";
    let options = this._argv[1] || {};
    this.force = options.force || false;
    log.verbose("command init options", options);
  }

  async exec() {
    // 1. 准备阶段
    // 2. 下载模板
    // 3. 安装模板
    try {
      const configInfo = await this.prepare();
      log.verbose("configInfo", configInfo);
      if (configInfo) {
        await this.downloadTemplate();
        await this.installTemplate();
      }
    } catch (e) {
      log.error(e.message);
    }
  }

  async installTemplate() {
    log.verbose("templateInfo", this.templateInfo);
    if (this.templateInfo) {
      if (!this.templateInfo.type) {
        this.templateInfo.type = TEMPLATE_TYPE_NORMAL;
      }

      if (this.templateInfo.type === TEMPLATE_TYPE_NORMAL) {
        // 标准安装
        await this.installNormalTemplate();
      } else if (this.templateInfo.type === TEMPLATE_TYPE_CUSTOM) {
        // 自定义安装
        await this.installCustomTemplate();
      } else {
        throw new Error("项目模板类型无法识别");
      }
    } else {
      throw new Error("项目模板信息不存在");
    }
  }

  // 下载模板
  async downloadTemplate() {
    // 管理方案
    // 1. 通过项目模板API获取项目模板信息
    // 1.1 创建一个gitee仓库专门管理项目模板
    // 1.2 通过npm存储项目模板
    // 1.3 将项目模板信息放在gitee仓库的template.json中
    // 1.4 通过请求获取template.json中的模板信息
    const { configTemplate } = this.configInfo;
    const templateInfo = this.template.find(
      item => item.npmName === configTemplate
    );

    const targetPath = path.resolve(userHome, ".test-cli", "template");
    const storeDir = path.resolve(
      userHome,
      ".test-cli",
      "template",
      "node_modules"
    );

    const { npmName, version } = templateInfo;
    const templateNpm = new Package({
      targetPath,
      storeDir,
      packageName: npmName,
      packageVersion: version
    });

    this.templateInfo = templateInfo;

    if (!(await templateNpm.exits())) {
      const spinner = spinnerStart("模板下载中...");
      await sleep(2000);
      try {
        await templateNpm.install();
      } catch (e) {
        throw e;
      } finally {
        spinner.stop(true);
        if (await templateNpm.exits()) {
          log.info("success", "模板下载成功");
          this.templateNpm = templateNpm;
        }
      }
    } else {
      const spinner = spinnerStart("模板更新中...");
      await sleep(1000);
      try {
        await templateNpm.update();
      } catch (e) {
        throw e;
      } finally {
        spinner.stop(true);
        if (await templateNpm.exits()) {
          log.info("success", "模板更新成功");
          this.templateNpm = templateNpm;
        }
      }
    }
  }

  // 检查cmd命令是否在白名单类
  checkCommand(cmd) {
    return WHITE_COMMAND.includes(cmd) ? cmd : null;
  }

  async execCommand(command, errMsg) {
    const installCmd = command.split(" ");
    const cmd = this.checkCommand(installCmd[0]);
    const args = installCmd.slice(1);

    if (!cmd) {
      throw new Error(`命令不合法，命令: ${command}`);
    }

    const ret = await execAsync(cmd, args, {
      stdio: "inherit",
      cwd: process.cwd()
    });

    if (ret !== 0) {
      throw new Error(errMsg);
    }
  }

  async ejsRender(options) {
    const dir = process.cwd();
    return new Promise((resolve, reject) => {
      require("glob")(
        "**",
        {
          cwd: dir,
          ignore: options.ignore || "",
          nodir: true
        },
        (err, files) => {
          if (err) reject(err);

          Promise.all(
            files.map(file => {
              const filePath = path.join(dir, file);
              return new Promise((resolve1, reject1) => {
                require("ejs").renderFile(
                  filePath,
                  this.configInfo,
                  (err, result) => {
                    if (err) {
                      reject1(err);
                    }
                    if (result) {
                      fse.writeFileSync(filePath, result);
                      resolve1(result);
                    }
                  }
                );
              });
            })
          )
            .then(() => {
              resolve();
            })
            .catch(e => {
              reject(e);
            });
        }
      );
    });
  }

  // 安装标准模板
  async installNormalTemplate() {
    // 1. 复制代码到当前目录
    // 2. 依赖安装
    // 3. 启动项目
    log.verbose("templateNpm", this.templateNpm);
    let spanner = spinnerStart("正在安装模板...");
    try {
      const templatePath = path.resolve(
        this.templateNpm.cacheFilePath,
        "template"
      );
      const targetPath = process.cwd();

      // 确保目录存在，不存在则创建
      fse.ensureDirSync(templatePath);
      fse.ensureDirSync(targetPath);

      fse.copySync(templatePath, targetPath);
    } catch (e) {
      throw e;
    } finally {
      spanner.stop(true);
      log.info("success", "模板安装成功");
    }

    // ejs模板渲染
    await this.ejsRender({
      ignore: ["node_modules/**", ...this.templateInfo.ignore]
    });

    // 依赖安装
    const { installCommand, startCommand } = this.templateInfo;

    // 启动命令
    await this.execCommand(installCommand, "依赖安装失败");
    log.info("success", "依赖安装成功");
    await this.execCommand(startCommand, "项目模板启动失败");
  }

  // 安装自定义模板
  async installCustomTemplate() {
    console.log("安装自定义模板");
  }

  // 准备
  async prepare() {
    // 0. 判断项目模板是否存在
    // 1. 判断是否是源码目录
    // 2. 判断当前目录是否为空
    // 3. 是否强制更新
    // 4. 选择创建项目/组件
    // 5. 获取项目基本信息

    const template = await getTemplate();
    if (!template || template.length === 0) {
      throw new Error("项目模板不存在");
    }
    this.template = template;

    const localPath = process.cwd();

    if (localPath.includes(process.env.CLI_BASE_PROJECT_PATH)) {
      throw new Error(
        "脚手架源码所在目录，不能初始化",
        process.env.CLI_BASE_PROJECT_PATH
      );
    }

    if (!this.isDirEmpty(localPath)) {
      let ifContinue = false;

      if (!this.force) {
        // 询问是否继续创建
        ifContinue = (
          await inquirer.prompt({
            type: "confirm",
            name: "ifContinue",
            default: false,
            message: "当前目录不为空，是否继续创建？"
          })
        ).ifContinue;

        if (!ifContinue) {
          return;
        }
      }

      if (ifContinue || this.force) {
        // 强制更新
        const { confirmDelete } = await inquirer.prompt({
          type: "confirm",
          name: "confirmDelete",
          default: false,
          message: "是否确认清空当前目录下的文件？"
        });

        if (confirmDelete) {
          // 清空当前目录
          fse.emptyDirSync(localPath);
        }
      }

      return this.getConfigInfo();
    } else {
      return this.getConfigInfo();
    }
  }

  // 获取选择的配置信息
  async getConfigInfo() {
    let configInfo = {};

    function isConfigNameValid(v) {
      return /^[a-zA-Z]+([-][a-zA-Z]+[a-zA-Z0-9]*|[_][a-zA-Z]+[a-zA-Z0-9]|[a-zA-Z0-9])$/.test(
        v
      );
    }

    // 选择创建项目/组件
    const { type } = await inquirer.prompt({
      type: "list",
      name: "type",
      message: "请选择初始化类型",
      default: TYPE_PROJECT,
      choices: [
        {
          name: "项目",
          value: TYPE_PROJECT
        },
        {
          name: "组件",
          value: TYPE_COMPONENT
        }
      ]
    });

    let configPrompt = [];

    const title = type === TYPE_PROJECT ? "项目" : "组件";

    if (!isConfigNameValid(this.configName)) {
      configPrompt.push({
        type: "input",
        message: `请输入${title}名称`,
        name: "configName",
        default: "",
        validate: function (v) {
          // 1. 首字符必须为英文字符
          // 2. 尾子符必须为英文/数字
          // 3. 字符允许 "-" "_"
          const done = this.async();

          setTimeout(function () {
            if (!isConfigNameValid(v)) {
              done(`${title}名称不合法`);
              return;
            }

            done(null, true);
          });
        },
        filter: function (v) {
          return v;
        }
      });
    } else {
      configInfo.configName = this.configName;
    }

    configPrompt.push({
      type: "input",
      message: `请输入${title}版本号`,
      name: "configVersion",
      default: "1.0.0",
      validate: function (v) {
        const done = this.async();

        setTimeout(function () {
          if (!!!semver.valid(v)) {
            done("版本号不合法");
            return;
          }

          done(null, true);
        });
        return;
      },
      filter: function (v) {
        const version = semver.valid(v);
        if (version) {
          return version;
        }
        return v;
      }
    });

    if (type === TYPE_COMPONENT) {
      configPrompt.push({
        type: "input",
        message: `请输入${title}描述信息`,
        name: "configDescription",
        default: "",
        validate: function (v) {
          const done = this.async();

          setTimeout(function () {
            if (!v.length) {
              done(`请填写${title}描述信息`);
              return;
            }

            done(null, true);
          });
        }
      });
    }

    configPrompt.push({
      type: "list",
      name: "configTemplate",
      message: `请选择${title}模板`,
      choices: this.createTemplateChoices(type)
    });

    log.verbose("type", type);

    const project = await inquirer.prompt(configPrompt);

    configInfo = {
      ...configInfo,
      type,
      ...project
    };

    // 数据兼容
    if (configInfo.configName) {
      configInfo.name = require("kebab-case")(configInfo.configName).replace(
        /^-/,
        ""
      );
    }

    if (configInfo.configVersion) {
      configInfo.version = configInfo.configVersion;
    }

    if (configInfo.configDescription) {
      configInfo.description = configInfo.configDescription;
    }

    this.configInfo = configInfo;

    return configInfo;
  }

  // 当前目录是否为空 抛开缓存文件
  isDirEmpty(localPath) {
    let fileList = fs.readdirSync(localPath);
    fileList = fileList.filter(file => {
      return !file.startsWith(".") && ["node_modules"].indexOf("file") < 0;
    });

    return !fileList || !fileList.length;
  }

  createTemplateChoices(type) {
    return this.template
      .filter(item => {
        return item.tag.includes(type);
      })
      .map(item => {
        return {
          value: item.npmName,
          name: item.name
        };
      });
  }
}

function init(argv) {
  new InitCommand(argv);
}

module.exports = init;
module.exports.InitCommand = InitCommand;
